import { SUB } from '../decimal/index'

export const formatTime = (time, hms = true, { dateUnit = '-', hmsUnit = ':' } = {}) => {
  const date = time instanceof Date ? time : new Date(parseFloat(time))

  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()

  const text = `${[year, month, day].map(formatNumber).join(dateUnit)}`

  if (!hms) return text
  
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return `${text} ${[hour, minute, second].map(formatNumber).join(hmsUnit)}`
}

export const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : `0${n}`
}

/**
 * 拼接链接和参数
 * @param {String} url 链接
 * @param {Object} params 链接参数
 */
export function joinUrlParams (url = '', params = {}) {
  if (!params || Object.keys(params).length === 0) return url
  
  let urlWithArgs = `${url}?`

  for (let key in params) {
    urlWithArgs += `&${key}=${params[key]}`
  }

  return urlWithArgs
}

/** 获取当前页url */
export function getCurrentPageUrl () {
  var pages = getCurrentPages() //获取加载的页面
  var currentPage = pages[pages.length - 1] //获取当前页面的对象
  var url = currentPage.route //当前页面url
  return url
}

/** 获取当前页带参数的url */
export function getCurrentPageUrlWithArgs () {
  var pages = getCurrentPages() //获取加载的页面
  var currentPage = pages[pages.length - 1] //获取当前页面的对象
  var url = currentPage.route //当前页面url
  var options = currentPage.options //如果要获取url中所带的参数可以查看options

  return joinUrlParams(url, options)
}

/**
 * 获取页面实例
 * @param {String} route 页面路径
 */
export const getPageInstance = route => {
  return getCurrentPages().filter(item => {
    return item.route == route
  })[0]
}

/**
 * 通过页面实例调用页面里的方法
 * @param {String} route 页面路径
 * @param {String} method 方法名
 * @param {any} params 需要传递的参数
 */
export const runPageMethod = (route = '', method = '', params) => {
  const page = getPageInstance(route)

  if (!page || !page[method] || typeof page[method] !== 'function') return

  return page[method](params)
}

/**
 * 返回上一级页面并获得页面实例，无法返回上一级会返回false
 * @param {number} delta 返回第几页
 * @param {function} callback 更新页面的回调
 */
export const afterPageUpdata = (delta, callback) => {
  const pages = getCurrentPages()
  if (pages.length === 1) return false

  const afterPage = pages[pages.length - (delta + 1)]

  if (afterPage) {
    (callback && typeof (callback) === 'function') && callback(afterPage)
  }

  wx.navigateBack({
    delta
  });

  return true
}

/**
 * 防抖函数
 * @param {function} fn 回调函数
 * @param {num} wait 间隔时间
 * @param {Boolean} immediate 第一次是否执行
 */
export const debounce = (fn, wait = 50, immediate) => {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate && !timer) {
      fn.apply(this, args)
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

/**
 * 节流函数
 * @param  {Function} fn   [是需要执行的函数]
 * @param  {Number}   wait [是时间间隔]
 */
export const throttle = (fn, wait = 50) => {
  let previous = 0
  return function (...args) {
    let now = +new Date()
    if (now - previous > wait) {
      previous = now
      fn.apply(this, args)
    }
  }
}

/**
 * 方法锁，使方法在执行过程中无法重复调用
 * @param {function} fn 要执行的方法
 * @returns {function}
 */
export function methodLock (fn) {
  let running = false

  return function (...args) {
    if (running) {
      console.error('该方法正在执行中')
      return
    }

    running = true

    const res = fn.apply(this, args)

    if (res instanceof Promise) {
      res.then(() => {
        running = false
      }).catch(err => {
        console.error(err)
        running = false
      })
    } else {
      running = false
    }

    return res
  }
}

/**
 * 生成n-m的随机整数
 * @param  {num} n 最小值
 * @param  {num} m 最大值
 * @return {num}   随机结果
 */
export const randomFn = (n, m) => {
  var w = m - n;
  var w1 = Math.round(Math.random() * w + n);
  return w1;
}

/**
 * 精确的几位小数
 * @param {num} n 数字
 * @param {num} decimals 精确到多少位
 */
export const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);

/**
 * 解决toFixed()精度问题
 * @param {Number} money 数字
 * @param {Number} decimals 精确到多少位
 */
export const formatToFixed = (money, decimals = 2) => {
  return (
    Math.round((parseFloat(money) + Number.EPSILON) * Math.pow(10, decimals)) /
    Math.pow(10, decimals)
  ).toFixed(decimals);
}

/**
 * 格式化价格数额为字符串
 * 可对小数部分进行填充，默认不填充
 * @param price 价格数额，以分为单位!
 * @param fill 是否填充小数部分 0-不填充 1-填充第一位小数 2-填充两位小数
 * @param symbol 单位
 * @param isIntZero 整数是否补零
 * @param isSplit 是否使用，分割
 */
export function formatPrice (price, fill = 2, symbol = '', isIntZero = true, isSplit = true) {
  if (isNaN(price) || price === null || price === Infinity) return price

  let p = price
  if (!isIntZero && Number.isInteger(parseFloat(price))) p = price.toString().split('.')[0]
  else p = formatToFixed(price, fill)

  if (isSplit) p = p.replace(/\B(?=(\d{3})+\b)/g, ",")

  return p.replace(/^/, `${symbol}`)
}

/**
 * 临时链接生成Base64
 */
export const getBase = url => {
  const fileManager = wx.getFileSystemManager()
  const fileContent = fileManager.readFileSync(url, 'base64')
  return `data:image/jpg;base64,${fileContent}`
}

/**
 * 平铺数组
 * @param {array} arr 被平铺数组
 * @param {num}} depth 平铺深度
 */
export const flatten = (arr, depth = 1) =>
  depth != 1 ?
  arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), []) :
  arr.reduce((a, v) => a.concat(v), []);

  /**
 * 检测并运行回调函数
 * @param {fn} callback 回调
 * @param {*} opt 回调传参
 */
export function runCallback (callback, opt) {
  return (callback && typeof callback === 'function') && callback(opt)
}

/**
 * 快捷跳转页面
 * @param {*} e 事件对象；设置data-url（跳转路径），data-target（跳转方式），data-params（参数）获取
 */
export function quickJumpPage (e) {
  const {
    url = '',
    target = 'navigateTo',
    params = {}
  } = e.currentTarget.dataset

  if (!url) return

  wx[target]({
    url: joinUrlParams(url, params)
  })
}

/**
 * 快捷跳转任意路径（包括小程序路径）
 * @param {*} e 事件对象；设置data-jump{ adv_type, adv_typedate, mini_program }获取；adv_type === 'navigateToMiniProgram'为跳转小程序
 */
export function quickJumpPath (e) {
  const { jump } = e.currentTarget.dataset
  if (!jump) return

  const {
    adv_type = 'navigateTo',
    adv_typedate,
    mini_program
  } = jump

  if (!adv_typedate || (adv_type === 'navigateToMiniProgram' && !mini_program)) return

  if (adv_type === 'navigateToMiniProgram' && mini_program) {
    wx.navigateToMiniProgram({
      appId: mini_program.appid,
      path: mini_program.adv_typedate
    })

    return
  }

  wx[adv_type]({
    url: adv_typedate
  })
}

/** 快捷设置剪贴板 */
export function quickSetClipboard (e, success) {
  const { text } = e.currentTarget.dataset

  wx.setClipboardData({
    data: text || '',
    success (res) {
      runCallback(success, res)
    },
    fail (err) {
      console.log(err)
    }
  })
}

/** 快捷全屏预览图片 */
export function quickPreviewImage (e) {
  const {
    urls = [],
    showmenu = true,
    current
  } = e.currentTarget.dataset

  wx.previewImage(Object.assign({
    urls,
    showmenu
  }, current && {
    current
  }))
}

/**
 * 获取组件实例（会根据id赋值给self实例上，注意相同key覆盖问题）
 * @param {String} id 组件的id
 * @param {Object} self 页面实例
 */
export function getComponentContext (id = '', self) {
    if (id === '') return
    const _self = self || this

    if (_self[id]) return _self[id]
    
    _self[id] = _self.selectComponent(`#${id}`)
    return _self[id]
}

/**
 * 判断一个值是否为空
 * @param {*} val 要判断的值
 * @returns Boolean
 */
export function commonEmptyJudge (val) {
  if (Array.isArray(val)) return val.length === 0
  else if (Object.prototype.toString.call(val) === '[object Object]') return Object.keys(val).length === 0
  else return !val
}

/**
 * 处理 path， path有三种形式：'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c']，需要统一处理成数组，便于后续使用
 * @param {Array, String} path 数据路径
 */
export function formatObjPath (path) {
  // 若是数组，则直接返回
  if (Array.isArray(path)) return path
  // 若有 '[',']'，则替换成将 '[' 替换成 '.',去掉 ']'
  return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
}

/**
 * 从对象中检索出给定数据路径的值
 * @param {object} object 源对象
 * @param {Array, String} path 数据路径
 * @param {*} defaultValue 没找到值时的默认值
 */
export function getObjectVal (object, path, defaultValue = undefined) {
  // 判断 object 是否是数组或者对象，否则直接返回默认值 defaultValue
  if (typeof object !== 'object') return defaultValue
  // 沿着路径寻找到对应的值，未找到则返回默认值 defaultValue
  return formatObjPath(path).reduce((o, k) => (o || {})[k], object) || defaultValue
}

/**
 * 通过数据路径设置对象
 * @param {Object} object 源对象
 * @param {Array, String} path 数据路径
 * @param {any} value 赋予的值
 */
export function setObjectVal (object, path, value) {
  if (typeof object !== 'object') return object;
  formatObjPath(path).reduce((o, k, i, _) => {
    if (i === _.length - 1) { // 若遍历结束直接赋值
      o[k] = value
      return null
    } else if (k in o) { // 若存在对应路径，则返回找到的对象，进行下一次遍历
      return o[k]
    } else { // 若不存在对应路径，则创建对应对象，若下一路径是数字，新对象赋值为空数组，否则赋值为空对象
      o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
      return o[k]
    }
  }, object)
  // 返回object
  return object;
}

/**
 * 设置全局数据
 * @param {String} key 键名
 * @param {*} val 
 */
export function setGlobalData (key = '', val) {
  if (!(typeof key === 'string')) return
  getApp().globalData[key] = val
}

/**
 * 获取全局数据
 * @param {String} key 键名
 */
export function getGlobalData (key = '') {
  if (!(typeof key === 'string')) return
  return getApp().globalData[key]
}

/**
 * 获取一次性的全局数据
 * @param {string} key 键名
 * @param {any} defaultVal 默认值
 */
export function getOnceGlobalData(key = '', defaultVal) {
  if (!(typeof key === 'string')) return

  const app = getApp()
  const val = app.globalData[key]

  app.globalData[key] = defaultVal

  return val
}

/**
 * 切割卡号
 * @param {String} num 卡号
 */
export function cuttingCardNum (num = '') {
  return (num).toString().replace(/(\d{4})/g, '$1 ').trim()
}

/**
 * 将字符串的一部分替换为指定的符号，只保留原始字符串的前几个和后几个字符可见。
 * @param {string} str - 要修改的输入字符串。
 * @param {Object} options - 包含可选参数的对象。
 * @param {number} [options.start=4] - 要在字符串开头保留的字符数。
 * @param {number} [options.end=4] - 要在字符串结尾保留的字符数。
 * @param {string} [options.symbol='*'] - 用于替换隐藏字符的符号。
 * @returns {string} 修改后的字符串。
 */
export function hiddenString (str = '', { start = 4, end = 4, symbol = '*' } = {}) {
  // 如果字符串长度小于等于 `start` 和 `end` 的总和，则只保留原始字符串的前一个和后一个字符可见。
  if (str.length <= start + end) {
    return str[0] + symbol.repeat(str.length - 2) + str[str.length - 1]
  }

  // 创建一个正则表达式，该正则表达式匹配以 `start` 个字符开头，后跟任意数量的字符，并以 `end` 个字符结尾的字符串部分。
  const reg = new RegExp('(\\w{' + start + '})\\w*(\\w{' + end + '})', 'g')

  // 计算将用符号替换的字符数。
  const diff = str.length - (start + end)

  // 使用 `replace` 方法将匹配的字符串部分替换为指定的符号。
  return str.replace(reg, `$1${symbol.repeat(diff <= 0 ? 1 : diff)}$2`)
}

/**
 * 休眠，延迟执行异步函数
 * @param {Number} ms 延迟时间
 */
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

/**
 * 判断是否为对象
 * @param {any} obj 判断的值
 */
export function isObject (obj) {
  return Object.prototype.toString.call(obj) ===  "[object Object]"
}

/**
 * 合并两个对象(深度合并)
 * @param {Object} obj1 
 * @param {Object} obj2 
 */
export function mergeObjects(obj1, obj2) {
  // 创建一个新对象，用于存储合并结果
  let mergedObj = {};

  // 合并 obj1 的属性
  for (let key in obj1) {
    mergedObj[key] = obj1[key];
  }

  // 合并 obj2 的属性
  for (let key in obj2) {
    // 如果 mergedObj 已经有相同的属性，则进行合并处理
    if (mergedObj.hasOwnProperty(key)) {
      // 如果属性值是对象，则递归调用 mergeObjects 进行深度合并
      if (typeof obj2[key] === 'object' && obj2[key] !== null) {
        mergedObj[key] = mergeObjects(mergedObj[key], obj2[key]);
      } else {
        // 否则直接赋值属性值
        mergedObj[key] = obj2[key];
      }
    } else {
      // 如果 mergedObj 没有相同的属性，则直接赋值属性值
      mergedObj[key] = obj2[key];
    }
  }

  return mergedObj;
}

/**
 * 获取节点事件dataset
 * @param {object} event 事件对象
 */
export function getEventDataset (event, key = '') {
  return key ? event.currentTarget.dataset[key] : (event.currentTarget.dataset || {})
}

/**
 * 获取节点事件传参
 * @param {object} event 事件对象
 */
export function getEventDetail (event, key = '') {
  return key ? event.detail[key] : (event.detail || {})
}

/**
 * 获取顶部navbar高度
 */
export function getNavbarHeight () {
  const { top, height } = wx.getMenuButtonBoundingClientRect()
  const { statusBarHeight } = wx.getSystemInfoSync()

  return statusBarHeight + ((top - statusBarHeight) * 2 + height) - 1
}

/**
 * rpx转px
 * @param {Number} rpx
 */
export function getRpxToPx (rpx) {
  if (!rpx) return 0
  const { screenWidth } = wx.getSystemInfoSync()

  return rpx * (screenWidth / 750)
}

/**
 * 快捷使用微信内置地图查看位置
 * @param {Object} e 事件对象 data-latitude, longitude, scale, name, address
 */
export function quickOpenLocation (e) {
  const {
    latitude,
    longitude,
    scale = 16,
    name = '',
    address = ''
  } = getEventDataset(e)

  if (!latitude || !longitude) return

  wx.openLocation({
    latitude: parseFloat(latitude),
    longitude: parseFloat(longitude),
    scale,
    name,
    address
  })
}

/**
 * 转换为正确的日期字符串
 * @param {String} dateStr 日期字符串
 * @param {String} hms 时分秒
 */
export function exchangeProperDateStr (dateStr = '', hms = '00:00:00') {
  return `${dateStr.replace(/-/g, '/')} ${hms}`
}

/**
 * 获取时间的年月日时分秒星期
 * @param {Date | Number | String} time 时间戳或者Date对象或者日期字符串
 * @param {Boolean} hms 是否显示时分秒
 */
export function getDateObj (time, hms = false, weekObj = { 0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六' }) {
  if (!time) return {}
  const date = time instanceof Date ? time : new Date(time)

  return Object.assign({
    year: date.getFullYear(),
    month: formatNumber(date.getMonth() + 1),
    day: formatNumber(date.getDate()),
    time: date.getTime(),
    week: weekObj[date.getDay()]
  }, hms && {
    hour: formatNumber(date.getHours()),
    minute: formatNumber(date.getMinutes()),
    second: formatNumber(date.getSeconds())
  })
}

/**
 * 获取未来多少天的日期集合
 * @param {Number} days 未来多少天
 */
export function getManyFutureDate (days = 15) {
  const dateArr = []

  for (let i = 0; i < days; i++) {
    const date = new Date()
    date.setDate(date.getDate() + i)
    date.setHours(0, 0, 0, 0)
    dateArr.push(getDateObj(date))
  }

  return dateArr
}

/**
 * 获取特定日期组
 * @param {Date} date 指定日期
 * @param {number} day 指定获取范围内的特定日期，正数是后几天，负数是前几天
 */
export function getSpecialDate (date = new Date(), day) {
  const y = date.getFullYear()
  const m = date.getMonth()
  const d = date.getDate()
  const w = date.getDay()

  date.setHours(0, 0, 0, 0)

  return Object.assign({
    monthStart: (new Date(y, m, 1)).getTime(),
    monthEnd: (new Date(y, m + 1, 0)).getTime(),
    weekStart: new Date(date).setDate(d - w + (w === 0 ? -6 : 1)),
    weekEnd: new Date(date).setDate(d - w + 7),
    yearStart: (new Date(y, 0, 1)).getTime(),
    yearEnd: (new Date(y, 12, 0)).getTime()
  }, Number.isInteger(day) && {
    dayOffset: new Date(date).setDate(d + day)
  })
}

/**
 * 获取倒计时
 * @param {Number,String} start 开始时间 ms
 * @param {Number,String} current 当前时间 ms
 * @param {Number,String} total 总时间 ms
 */
export function getCountdown (start, current, total) {
  const diff = SUB(total, SUB(current, start))

  return diff <= 0  ? 0 : diff
}

/**
 * 生成唯一ID（简单）
 */
export function createUniqueId () {
  return Date.now().toString() + (Math.floor(Math.random() * 10000)).toString()
}

/**
 * 获取数组中所有符合条件的元素索引
 * @param {array} list 检查数组
 * @param {function} fn 检查方法
 */
export function filterIndexs (list = [], fn) {
  if (list.length === 0 || typeof fn !== 'function') return []
  return list.reduce((arr, item, index) => {
    if (fn(item, index)) arr.push(index)

    return arr
  }, [])
}

/**
 * 版本号对比（1是大于，-1是小于，0是等于）
 * @param {string} version1 当前版本号
 * @param {string} version2 对比版本号
 */
export function compareVersions(version1, version2) {
  const parts1 = version1.split('.');
  const parts2 = version2.split('.');
  
  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
      const num1 = parseInt(parts1[i] || 0);
      const num2 = parseInt(parts2[i] || 0);
      
      if (num1 < num2) {
          return -1;
      } else if (num1 > num2) {
          return 1;
      }
  }
  
  return 0; // Versions are equal
}
